import keyConst from "../constant/key.const"
import unitTypeEnum from "../enum/unitType.enum"
import { OutOfRangeException } from "../exception/range.exception"

const waitRun = function (time) {
  return new Promise(resolve => setTimeout(resolve, time))
}
const waitRunX = time => {
  let done,
    timeIndex = -1
  return {
    promise: new Promise(resolve => {
      done = resolve
      timeIndex = window.setTimeout(done, time)
    }).then(() => {
      timeIndex = -1
    }),
    cancel: () => {
      timeIndex !== -1 && (window.clearTimeout(timeIndex), done())
    }
  }
}
const arrayToObj = function (array) {
  const t = {}
  for (const i of array) t[i] = i
  return t
}
const textLen = function (text) {
  return !text.replace(/\n/g, "").trim().length
}
const sameMap = function (e, t) {
  if (e.size !== t.size) return !1
  for (const i of e) if (!t.has(i)) return !1
  return !0
}
const includeArray = function (e, t) {
  const i = Math.min(e.length, t.length)
  for (let n = 0; n < i; n++) if (e[n] !== t[n]) return !1
  return !0
}
const getArrayVal = function (e, t, i = !0) {
  if (0 === e.length) return null
  i &&
    e.sort(function (e, t) {
      return e - t
    })
  const n = t / 100
  return e[Math.floor(e.length * n)]
}
const renderTime = function (e) {
  return [
    numToStr(e.getMonth() + 1),
    ".",
    numToStr(e.getDate()),
    ".",
    e.getFullYear(),
    "_",
    numToStr(e.getHours()),
    ".",
    numToStr(e.getMinutes()),
    ".",
    numToStr(e.getSeconds())
  ].join("")
}
const numToStr = function (e, t = 2, i = "0", n = 10) {
  const s = e.toString(n),
    r = t - s.length + 1
  return r > 0 ? new Array(r).join(i) + s : s
}
const randomStr = function (e = 1) {
  let t = ""
  const i = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
  for (let n = 0; n < e; n++) t += i.charAt(Math.floor(Math.random() * i.length))
  return t
}
const base64Str = e => {
  const t: any[] = []
  for (let i = 0; i < e.length; i += 8192) t.push(String.fromCharCode.apply(null, e.subarray(i, i + 8192)))
  return btoa(t.join(""))
}
const valToFalse = (e = []) => {
  const t: any = {}
  for (const i of e) t[i] = !0
  return t
}
const hasOwnProperty = (e, t) => {
  return e.hasOwnProperty(t)
}
const substrNum = (e, t, i = "...") => (e.length < t ? e : `${e.substr(0, t)}${i}`)
const checkFunc = (e, t, i) => {
  if (!(Math.abs(e) < 4294967295) || !(t < 32) || !(i < 32)) throw new OutOfRangeException("argument out of range")
  const s = 1 & ((e >>> t) ^ (e >>> i))
  return (e ^= (s << t) | (s << i)) >>> 0
}
const addNR = (...e) => {
  return e.join("\n")
}
const getUrlParams = function (key: string, defaultVal?: any) {
  const meshes = new RegExp("[#&?]" + key + "=([^#&?]*)").exec(window.location.href)
  if (!meshes) return defaultVal
  const val = meshes[1]
  return "boolean" == typeof defaultVal ? "true" === val || "1" === val : "number" == typeof defaultVal ? parseFloat(val) : decodeURIComponent(val)
}
const numberType = e => {
  switch (e) {
    case "int8":
      return Int8Array
    case "int16":
      return Int16Array
    case "int32":
      return Int32Array
    case "uint8":
      return Uint8Array
    case "uint16":
      return Uint16Array
    case "uint32":
      return Uint32Array
    case "float32":
      return Float32Array
    case "float64":
      return Float64Array
    case "array":
      return Array
    case "uint8_clamped":
      return Uint8ClampedArray
  }
  return Array
}

const ignoredKey = prop => {
  return (
    prop === "propertyObservers" ||
    prop === "changeObservers" ||
    prop === "isObservable" ||
    prop === "childChangeFunctions" ||
    prop === "isObservableProxy" ||
    prop === "diffRoot" ||
    prop === "elementChangedHandlers" ||
    prop === "knownKeysMap" ||
    prop === "knownKeysList" ||
    prop === "isVector3" ||
    prop === "onPropertyChanged" ||
    prop === "removeOnPropertyChanged" ||
    prop === "onChanged" ||
    prop === "target"
  )
}

const deepCopy = (obj, ancestors = []) => {
  if (obj === undefined) {
    return undefined
  }

  if (obj in ancestors) {
    return {}
  }
  if (obj instanceof Date) {
    return new Date(obj)
  }

  if (obj === null) {
    return null
  }
  if (obj.deepCopy) {
    return obj.deepCopy()
  }
  if (typeof obj === "object") {
    const output = Array.isArray(obj) || ArrayBuffer.isView(obj) ? [] : {}
    for (const key in obj) {
      if (ignoredKey(key)) {
        continue
      }
      const v = obj[key]
      if (v instanceof Date) {
        output[key] = new Date(v)
      } else if (typeof v !== "function") {
        output[key] = typeof v === "object" ? deepCopy(v, ancestors.concat(obj)) : v
      }
    }
    return output
  } else {
    return obj
  }
}
const merge = (a, b) => {
  for (const key in b) {
    if (typeof b[key] === "function") {
      continue
    }
    if (ignoredKey(key) && !a.hasOwnProperty(key)) {
      continue
    }
    const bVal = b[key]
    if (a.hasOwnProperty(key)) {
      const aVal = a[key]
      const aType = aVal === null ? null : typeof aVal
      const bType = bVal === null ? null : typeof bVal
      if (aType !== bType) {
        a[key] = deepCopy(bVal)
      } else if (typeof aVal === "object") {
        if (aVal instanceof Date) {
          a[key] = new Date(bVal)
        } else {
          merge(aVal, bVal)
        }
      } else {
        a[key] = bVal
      }
    } else {
      a[key] = deepCopy(bVal)
    }
  }
}

const diffObject = (original, newer) => {
  const out = {}
  for (const key in original) {
    const originalValue = original[key]
    const newerValue = newer[key]
    if (deepDiffers(originalValue, newerValue)) {
      if (originalValue === undefined || newerValue === undefined) {
        out[key] = newerValue
      } else if (typeof originalValue !== typeof newerValue) {
        out[key] = newerValue
      } else if (typeof originalValue !== "object") {
        out[key] = newerValue
      } else if (Array.isArray(newerValue)) {
        if (deepDiffers(originalValue, newerValue)) {
          out[key] = newerValue
        }
      } else {
        out[key] = diffObject(originalValue, newerValue)
      }
    }
  }
  for (const key in newer) {
    if (!(key in original)) {
      out[key] = newer[key]
    }
  }
  return out
}
const deepDiffers = (a, b) => {
  if (typeof a === "object") {
    if (!b) return true

    for (const key in b) if (!(key in a)) return true
    for (const key in a) {
      if (deepDiffers(a[key], b[key])) return true
    }
  } else {
    return a !== b
  }
  return false
}
function isNumber(x: any): x is number {
  return typeof x === "number"
}
//计算焦距
function focalDistance(e: number, t: number, i: number) {
  return (1 - i) * e + i * t
}
async function localType() {
  return { city: "city", country: "country", region: "region" }
}
function getMax(val1: number, val2: number, maxVal: number) {
  return Math.max(val2, Math.min(val1, maxVal))
}
const initResizeDimensions = (duration = 200) => ({
  resizeDimensions: [
    {
      property: keyConst.width,
      setDimension: e => e.width,
      duration
    },
    {
      property: keyConst.height,
      setDimension: e => e.height,
      duration
    },
    { property: keyConst.top, setDimension: () => 0, duration },
    { property: keyConst.left, setDimension: () => 0, duration }
  ]
})
const addResizeDimensions = (width: number, height: number, duration = 200) => {
  const resizeDimensions: any[] = []
  !!width &&
    resizeDimensions.push({
      property: keyConst.width,
      setDimension: t => t.width + width,
      duration
    })
  !!height &&
    resizeDimensions.push({
      property: keyConst.height,
      setDimension: e => e.height + height,
      duration
    })
  return { resizeDimensions }
}
const testPassive = (() => {
  let e = !1
  try {
    const t = Object.defineProperty({}, "passive", {
      get: () => {
        e = !0
        return e
      }
    })
    window.addEventListener("testPassive", () => {}, t)
    window.removeEventListener("testPassive", () => {}, t)
    return e
  } catch (e) {
    return !1
  }
})()
const isPosition = e => (e => "object" == typeof e && "x" in e && "y" in e)(e) && "z" in e
const isNullOBJ = e => !e || !Object.values(e).filter(e => 0 !== e).length
function unitType(e) {
  return e.match(/us|mm|lr$/i) ? unitTypeEnum.IMPERIAL : unitTypeEnum.METRIC
}
const throttle = (func, wait) => {
  let index
  let oldTime = Date.now() - wait
  return (...params) => {
    const space = Date.now() - oldTime,
      action = () => {
        func.apply(null, params)
        oldTime = Date.now()
      }
    if ((index && clearTimeout(index), space >= wait)) action()
    else {
      index = window.setTimeout(action, wait - space)
    }
  }
}
const debounce = (func: Function, delay = 250) => {
  let i: number
  return (...n: any) => {
    window.clearTimeout(i)
    i = window.setTimeout(() => func.apply(null, n), delay)
  }
}
import ModelColliderTarget from "../mesh/ModelColliderTarget"

const isModelColliderTarget = (e: any) => e instanceof ModelColliderTarget
const raycastVisible = (e: any) => isModelColliderTarget(e) && e.raycastEnabled && e.visible
const traverseAncestorsVisible = <T extends THREE.Mesh>(e: T) => {
  let t = e.visible
  t &&
    e.traverseAncestors(e => {
      t = e.visible
    })
  return (!isModelColliderTarget(e) && t) || raycastVisible(e)
}
function copyPostion(e) {
  return { x: e.x, y: e.y, z: e.z }
}
function getXYZW(e: any) {
  return {
    x: e.x || e._x,
    y: e.y || e._y,
    z: e.z || e._z,
    w: e.w || e._w
  }
}
function createColorString(e) {
  return `#${numToStr(255 * e.r, 2, "0", 16)}${numToStr(255 * e.g, 2, "0", 16)}${numToStr(255 * e.b, 2, "0", 16)}`
}

const getProtoName = e => e.prototype.constructor.name
const getConstructor = e => e.constructor
const getClassName = (e: Function) => {
  if ("string" == typeof e) return e
  if (e && e.name) return e.name
  const t = e.toString()
  return t.substring(0, t.indexOf("\n")).split(" ")[1]
}
export default {
  getProtoName,
  getConstructor,
  getClassName,
  waitRun,
  waitRunX,
  arrayToObj,
  textLen,
  sameMap,
  includeArray,
  getArrayVal,
  renderTime,
  numToStr,
  randomStr,
  base64Str,
  valToFalse,
  hasOwnProperty,
  substrNum,
  checkFunc,
  addNR,
  getUrlParams,
  numberType,
  ignoredKey,
  deepCopy,
  merge,
  diffObject,
  deepDiffers,
  isNumber,
  focalDistance,
  localType,
  getMax,
  initResizeDimensions,
  addResizeDimensions,
  testPassive,
  isPosition,
  isNullOBJ,
  unitType,
  throttle,
  debounce,
  isModelColliderTarget,
  raycastVisible,
  traverseAncestorsVisible,
  createColorString,
  getXYZW,
  copyPostion
}
